package cn.hy.mvc;

import cn.hy.mvc.anno.TinyAutowired;
import cn.hy.mvc.anno.TinyController;
import cn.hy.mvc.anno.TinyRequestMapping;
import cn.hy.mvc.anno.TinyService;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author huang.xy
 * @Description TODO
 */
public class DispatchServlet extends HttpServlet {


    private Properties properties;

    private Set<String> classNames = new HashSet<>();

    private Map<String, Object> ioc = new HashMap<>();

    private final List<Handler> handlers = new ArrayList<>();

    public void init() {
        loadProperties();
        doScan(properties.getProperty("basePackage"));
        doInstance();
        doAutowired();
        initHandler();
        System.out.println();
    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req,resp);
        String requestURI = req.getRequestURI();
        Handler curHandler = null;
        for (Handler handler : handlers) {
            Matcher matcher = handler.getPattern().matcher(requestURI);
            if (matcher.matches()) {
                curHandler = handler;
                break;
            }
        }
        if(Objects.isNull(curHandler)) return;

        Map<Object, Integer> parameterIndexMap = curHandler.getParameterIndexMap();
        Object[]  params = new Object[parameterIndexMap.size()];

        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            // a = 1 & a = 2  == > a = 1, 2
            String pV = String.join(",", entry.getValue());
            if (parameterIndexMap.containsKey(entry.getKey()))  {
                int index = parameterIndexMap.get(entry.getKey());
                params[index] = pV;
            }
        }
        if (parameterIndexMap.containsKey(HttpServletRequest.class.getSimpleName()) ) {
            params[parameterIndexMap.get(HttpServletRequest.class.getSimpleName())] = req;
        }

        if (parameterIndexMap.containsKey(HttpServletResponse.class.getSimpleName()) ) {
            params[parameterIndexMap.get(HttpServletResponse.class.getSimpleName())] = resp;
        }

        try {
            curHandler.getMethod().invoke(curHandler.getInstance(),params);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * url -method 的关系
     */
    private void initHandler() {
        if (ioc.isEmpty()) return;
        Set<Object> instanceSet = new HashSet<>(ioc.values());

        for (Object instance : instanceSet) {
            Class<?> aClass = instance.getClass();

            if (aClass.isAnnotationPresent(TinyController.class)) {
                String baseUrl = "";
                TinyRequestMapping annotation = aClass.getAnnotation(TinyRequestMapping.class);
                if (Objects.nonNull(annotation)) {
                    baseUrl += annotation.value();
                }
                Method[] declaredMethods = aClass.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {
                    String url = "";
                    if (declaredMethod.isAnnotationPresent(TinyRequestMapping.class)) {
                        TinyRequestMapping tinyRequestMapping = declaredMethod.getAnnotation(TinyRequestMapping.class);
                        url  =  baseUrl +  tinyRequestMapping.value();
                        Handler handler = new Handler(instance,declaredMethod, Pattern.compile(url));

                        Parameter[] parameters = declaredMethod.getParameters();
                        for (int i = 0; i < parameters.length; i++) {
                             Parameter parameter = parameters[i];
                            if (parameter.getType()  == HttpServletRequest.class ||parameter.getType() == HttpServletResponse.class) {
                                handler.getParameterIndexMap().put(parameter.getType().getSimpleName(),i);
                            }else {
                                handler.getParameterIndexMap().put(parameter.getName(),i);
                            }
                        }
                        handlers.add(handler);
                    }
                }

            }

        }


    }

    private void doAutowired() {
        if (ioc.isEmpty()) return;
        // ioc 去重
        Set<Object> instanceSet = new HashSet<>(ioc.values());
        try {
            for (Object instance : instanceSet) {
                Field[] declaredFields = instance.getClass().getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    if (declaredField.isAnnotationPresent(TinyAutowired.class) && !declaredField.getType().isPrimitive()) {
                        TinyAutowired annotation = declaredField.getAnnotation(TinyAutowired.class);
                        String value = annotation.value();
                        declaredField.setAccessible(true);
                        if ("".equals(value)) {
                            String beanName = declaredField.getName();
                            Object o = ioc.get(beanName);
                            if (Objects.isNull(o)) {
                                beanName = declaredField.getType().getName();
                                o = ioc.get(beanName);
                            }
                            if (o == null) continue;
                            declaredField.set(instance, o);
                        } else {
                            Object o = ioc.get(value);
                            if (o == null) {
                                throw new RuntimeException("not found instance ");
                            }
                            declaredField.set(instance, o);
                        }
                    }
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }


    }


    private void doInstance() {
        if (classNames.isEmpty()) return;
        try {

            for (String className : classNames) {
                Class<?> aClass = Class.forName(className);
                // 区分 controller 和 service
                if (aClass.isAnnotationPresent(TinyController.class)) {
                    // controller 直接存储 类名小写 和实例
                    String key = lowerFirst(aClass.getSimpleName());
                    ioc.put(key, aClass.newInstance());

                } else if (aClass.isAnnotationPresent(TinyService.class)) {
                    TinyService annotation = aClass.getAnnotation(TinyService.class);
                    String value = annotation.value();
                    // 不存在则使用接口
                    Object instance = aClass.newInstance();
                    if (value.equals("")) {
                        Class<?>[] interfaces = aClass.getInterfaces();
                        String key = lowerFirst(interfaces[0].getSimpleName()); // App
                        ioc.put(key, instance);
                    } else {
                        ioc.put(value, instance);
                    }
                    //通过接口注入 默认存储一份接口类型
                    Class<?>[] interfaces = aClass.getInterfaces();
                    if (interfaces.length == 0) {
                        continue;
                    }
                    String name = interfaces[0].getName();
                    ioc.put(name, instance);

                }
                //不是 do nothing
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    // ⾸字⺟⼩写⽅法
    public String lowerFirst(String str) {
        char[] chars = str.toCharArray();
        if ('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }


    private void doScan(String basePackage) {
        String basePackagePath = basePackage.replaceAll("\\.", "/");
        String path = this.getClass().getClassLoader().getResource("").getPath() + basePackagePath;
        File file = new File(path);
        File[] files = file.listFiles();
        for (File curFile : files) {
            if (curFile.isDirectory()) {
                doScan(basePackage + "." + curFile.getName());
            } else if (curFile.getName().endsWith(".class")) {
                //D:\work\ideaspace\tiny_mvc\target\classes\cn\hy\demo\App.class
                String className = curFile.getName().substring(0, curFile.getName().lastIndexOf(".class"));
                classNames.add(basePackage + "." + className);
            }
        }
    }

    private void loadProperties() {
        try {
            InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("mvc.properties");
            properties = new Properties();
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
